package com.eolwral.osmonitor;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.os.IBinder;
import android.support.v7.app.NotificationCompat;
import android.widget.RemoteViews;

import com.eolwral.osmonitor.core.cpuInfo;
import com.eolwral.osmonitor.core.cpuInfoList;
import com.eolwral.osmonitor.core.networkInfo;
import com.eolwral.osmonitor.core.networkInfoList;
import com.eolwral.osmonitor.core.osInfo;
import com.eolwral.osmonitor.core.processInfo;
import com.eolwral.osmonitor.core.processInfoList;
import com.eolwral.osmonitor.ipc.IpcService;
import com.eolwral.osmonitor.ipc.IpcService.ipcClientListener;
import com.eolwral.osmonitor.ipc.ipcCategory;
import com.eolwral.osmonitor.ipc.ipcData;
import com.eolwral.osmonitor.ipc.ipcMessage;
import com.eolwral.osmonitor.settings.Settings;
import com.eolwral.osmonitor.settings.Settings.NotificationType;
import com.eolwral.osmonitor.settings.Settings.StatusBarColor;
import com.eolwral.osmonitor.util.CoreUtil;
import com.eolwral.osmonitor.util.ProcessUtil;
import com.eolwral.osmonitor.util.UserInterfaceUtil;

import java.nio.ByteBuffer;
import java.util.Locale;

public class OSMonitorService extends Service implements ipcClientListener {
  private static final int NOTIFYID = 20100811;
  private IpcService ipcService = null;
  private int UpdateInterval = 2;

  private boolean isRegistered = false;
  private NotificationManager nManager = null;
  private NotificationCompat.Builder nBuilder = null;

  // process
  private int iconColor = 0;
  private int fontColor = 0;
  private int backgroundColor = 0;
  private boolean isSetTop = false;
  private float cpuUsage = 0;
  private float ioWaitUsage = 0;
  private float[] topUsage = new float[3];
  private String[] topProcess = new String[3];

  // memory
  private long memoryTotal = 0;
  private long memoryFree = 0;

  // battery
  private boolean useCelsius = false;
  private int battLevel = 0; // percentage value or -1 for unknown
  private int temperature = 0;

  // network
  private long trafficOut = 0;
  private long trafficIn = 0;

  // private
  private ProcessUtil infoHelper = null;
  private Settings settings = null;
  private int notificationType = NotificationType.MEMORY_BATTERY;

  @Override
  public IBinder onBind(Intent intent) {
    return null;
  }

  @Override
  public void onCreate() {

    super.onCreate();

    settings = Settings.getInstance(this);

    IpcService.Initialize(this);
    ipcService = IpcService.getInstance();

    refreshSettings();
    initializeNotification();

    if (settings.isEnableCPUMeter()) {
      infoHelper = ProcessUtil.getInstance(this, false);
      initService();
    }
  }

  private void refreshSettings() {

    // use notification for meta color
    if (CoreUtil.isGreaterThanMarshmallow()) {
      iconColor = R.drawable.ic_cpu_graph_meta;
    }
    else {
      switch (settings.getCPUMeterColor()) {
        case StatusBarColor.GREEN:
          iconColor = R.drawable.ic_cpu_graph_green;
          break;
        case StatusBarColor.BLUE:
          iconColor = R.drawable.ic_cpu_graph_blue;
          break;
        case StatusBarColor.WHITE:
          iconColor = R.drawable.ic_cpu_graph_meta;
        }
    }


    notificationType = settings.getNotificationType();
    fontColor = settings.getNotificationFontColor();
    backgroundColor = settings.getNotificationBackgroundColor();
    isSetTop = settings.isNotificationOnTop();
    useCelsius = settings.isUseCelsius();
    
    // recreate connection type when refreshing settings
    ipcService.createConnection();
  }

  @Override
  public void onDestroy() {
    endNotification();
    endService();
    if (!settings.getSessionValue().equals("Non-Exit"))
      android.os.Process.killProcess(android.os.Process.myPid());
    super.onDestroy();
  }

  private void endNotification() {
    nManager.cancel(NOTIFYID);
    stopForeground(true);
  }

  private void initializeNotification() {

    Intent notificationIntent = new Intent(this, OSMonitor.class);
    notificationIntent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
    PendingIntent contentIntent = PendingIntent.getActivity(
        this.getBaseContext(), 0, notificationIntent, 0);

    nManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    nBuilder = new NotificationCompat.Builder(this);
    nBuilder.setContentTitle(getResources().getText(R.string.ui_appname));
    nBuilder.setContentText(getResources().getText(R.string.ui_shortcut_detail));
    nBuilder.setOnlyAlertOnce(true);
    nBuilder.setOngoing(true);
    nBuilder.setContentIntent(contentIntent);
    nBuilder.setCategory(Notification.CATEGORY_STATUS);

    // Set infos as public
    nBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);

    // Use new style icon for Lollipop
    if (CoreUtil.isGreaterThanMarshmallow())
      nBuilder.setSmallIcon(R.drawable.ic_stat_notify);
    else
      nBuilder.setSmallIcon(R.drawable.ic_launcher);

    if (isSetTop)
      nBuilder.setPriority(NotificationCompat.PRIORITY_MAX);

    Notification osNotification = nBuilder.build();
    nManager.notify(NOTIFYID, osNotification);

    // set foreground to avoid recycling
    startForeground(NOTIFYID, osNotification);

  }

  private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {

      if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF))
        goSleep();
      else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON))
        wakeUp();
    }
  };

  private void registerScreenEvent() {
    IntentFilter filterScreenON = new IntentFilter(Intent.ACTION_SCREEN_ON);
    registerReceiver(mReceiver, filterScreenON);

    IntentFilter filterScreenOFF = new IntentFilter(Intent.ACTION_SCREEN_OFF);
    registerReceiver(mReceiver, filterScreenOFF);
  }

  private void initService() {
    if (!isRegistered) {
      registerScreenEvent();
      isRegistered = true;
    }

    wakeUp();
  }

  private void endService() {
    if (isRegistered) {
      unregisterReceiver(mReceiver);
      isRegistered = false;
    }

    goSleep();
  }

  private byte[] getReceiveDataType() {
    byte newCommand[] = { ipcCategory.PROCESS, ipcCategory.CPU, ipcCategory.OS };
    switch (notificationType) {
    case NotificationType.MEMORY_BATTERY:
      newCommand = new byte[] { ipcCategory.PROCESS, ipcCategory.OS };
      break;
    case NotificationType.MEMORY_DISKIO:
      newCommand = new byte[] { ipcCategory.PROCESS, ipcCategory.CPU, ipcCategory.OS };
      break;
    case NotificationType.BATTERY_DISKIO:
      newCommand = new byte[] { ipcCategory.PROCESS, ipcCategory.CPU };
      break;
    case NotificationType.NETWORKIO:
      newCommand = new byte[] { ipcCategory.PROCESS, ipcCategory.NETWORK };
      break;
    }
    return newCommand;
  }

  private void wakeUp() {
    UpdateInterval = settings.getInterval();
    byte newCommand[] = getReceiveDataType();
    ipcService.removeRequest(this);
    ipcService.addRequest(newCommand, 0, this);
    startBatteryMonitor();
  }

  private void goSleep() {
    ipcService.removeRequest(this);
    stopBatteryMonitor();
  }

  private void startBatteryMonitor() {
    if (notificationType != NotificationType.MEMORY_BATTERY
        && notificationType != NotificationType.BATTERY_DISKIO)
      return;

    if (!isRegisterBattery) {
      IntentFilter battFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
      registerReceiver(battReceiver, battFilter);
      isRegisterBattery = true;
    }
  }

  private void stopBatteryMonitor() {
    if (isRegisterBattery) {
      unregisterReceiver(battReceiver);
      isRegisterBattery = false;
    }
  }

  private boolean isRegisterBattery = false;

  private BroadcastReceiver battReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {

      int rawlevel = intent.getIntExtra("level", -1);
      int scale = intent.getIntExtra("scale", -1);

      temperature = intent.getIntExtra("temperature", -1);

      if (rawlevel >= 0 && scale > 0) {
        battLevel = (rawlevel * 100) / scale;
      }
    }
  };

  @Override
  public void onRecvData(byte [] result) {

    if (result == null) {
      byte newCommand[] = getReceiveDataType();
      ipcService.addRequest(newCommand, UpdateInterval, this);
      return;
    }

    // gather data
    cpuUsage = 0;

    // empty
    for (int index = 0; index < 3; index++) {
      topUsage[index] = 0;
      topProcess[index] = "";
    }

    try {
      ipcMessage ipcMessageResult = ipcMessage.getRootAsipcMessage(ByteBuffer.wrap(result));

      for (int index = 0; index < ipcMessageResult.dataLength(); index++) {
        ipcData rawData = ipcMessageResult.data(index);

        if (rawData.category() == ipcCategory.OS)
          extractOSInfo(rawData);
        else if (rawData.category() == ipcCategory.CPU)
          extractCPUInfo(rawData);
        else if (rawData.category() == ipcCategory.NETWORK)
          extractNetworkInfo(rawData);
        else if (rawData.category() == ipcCategory.PROCESS)
          extractProcessInfo(rawData);
      }

    } catch (Exception e) {}

    refreshNotification();

    // send command again
    byte newCommand[] = getReceiveDataType();
    ipcService.addRequest(newCommand, UpdateInterval, this);
  }

  private void extractProcessInfo(ipcData rawData)
  {

    processInfoList infoList = processInfoList.getRootAsprocessInfoList(rawData.payloadAsByteBuffer().asReadOnlyBuffer());
    for (int count = 0; count < infoList.listLength(); count++) {
      processInfo item = infoList.list(count);
      cpuUsage += item.cpuUsage();

      for (int check = 0; check < 3; check++) {

        if (topUsage[check] >= item.cpuUsage())
          continue;

        // keep top 3 usage
        for (int push = 2; push > check; push--) {
          topUsage[push] = topUsage[push - 1];
          topProcess[push] = topProcess[push - 1];
        }

        topUsage[check] = item.cpuUsage();
        topProcess[check] = infoHelper.getPackageName(item.name());

        // if name is not ready, use process name
        if (topProcess[check] == null)
          topProcess[check] = item.name();

        // check cached status
        if (infoHelper.checkPackageInformation(item.name()))
          break;

        // prepare to do cache
        if (item.name().toLowerCase(Locale.getDefault()).contains("osmcore"))
          infoHelper.doCacheInfo(android.os.Process.myUid(), item.owner(), item.name());
        else
          infoHelper.doCacheInfo(item.uid(), item.owner(), item.name());
        break;
      }

    }
  }

  private void extractNetworkInfo(ipcData rawData)
  {
    // process processInfo
    trafficOut = 0;
    trafficIn = 0;
    networkInfoList nwInfoList = networkInfoList.getRootAsnetworkInfoList(rawData.payloadAsByteBuffer().asReadOnlyBuffer());
    for (int count = 0; count < nwInfoList.listLength(); count++) {
      networkInfo nwInfo = nwInfoList.list(count);
      trafficOut += nwInfo.transUsage();
      trafficIn += nwInfo.recvUsage();
    }
  }

  private void extractCPUInfo(ipcData rawData)
  {
    cpuInfoList infoList = cpuInfoList.getRootAscpuInfoList(rawData.payloadAsByteBuffer().asReadOnlyBuffer());
    for(int count = 0; count < infoList.listLength(); count++) {
      cpuInfo info = infoList.list(count);
      ioWaitUsage = info.ioUtilization();
      break;
    }
  }

  private void extractOSInfo(ipcData rawData)
  {
    osInfo info = osInfo.getRootAsosInfo(rawData.payloadAsByteBuffer().asReadOnlyBuffer());
    memoryFree = info.freeMemory() + info.bufferedMemory() + info.cachedMemory();
    memoryTotal = info.totalMemory();
  }

  private String getBatteryInfo() {
    String info = "";
    if (useCelsius)
      info = battLevel + "% (" + temperature / 10 + "\u2103)";
    else
      info = battLevel + "% (" + ((int) temperature / 10 * 9 / 5 + 32) + "\u2109)";
    return info;
  }

  private void refreshNotification() {


    Notification osNotification;

    // set current iconLevel
    // Fix: Android 6.0 issue
    if (CoreUtil.isGreaterThanMarshmallow()) {
      int iconLevel = 0;

      if (cpuUsage < 20)
        iconLevel = 1;
      else if (cpuUsage < 40)
        iconLevel = 2;
      else if (cpuUsage < 60)
        iconLevel = 3;
      else if (cpuUsage < 80)
        iconLevel = 4;
      else if (cpuUsage < 100)
        iconLevel = 5;
      else
        iconLevel = 6;

      osNotification = nBuilder.setSmallIcon(iconColor, iconLevel).build();
    }
    else {
      osNotification = nBuilder.build();
    }

    osNotification.contentView = new RemoteViews(getPackageName(), R.layout.ui_notification);

    // set contentIntent to fix
    // "android.app.RemoteServiceException: Bad notification posted from package"
    Intent notificationIntent = new Intent(this, OSMonitor.class);
    notificationIntent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
    osNotification.contentIntent = PendingIntent.getActivity( this.getBaseContext(), 0, notificationIntent, 0);

    osNotification.contentView.setTextViewText(R.id.notification_cpu, "CPU: " + UserInterfaceUtil.convertToUsage(cpuUsage) + "%");
    osNotification.contentView.setProgressBar(R.id.notification_cpu_bar, 100, (int) cpuUsage, false);

    switch (notificationType) {

    case NotificationType.MEMORY_BATTERY:
      osNotification.contentView.setTextViewText(R.id.notification_1nd, "MEM: "
                                                 + UserInterfaceUtil.convertToSize(memoryFree, true));
      osNotification.contentView.setTextViewText(R.id.notification_2nd, "BAT: " + getBatteryInfo());
      osNotification.contentView.setProgressBar(R.id.notification_1nd_bar, (int) memoryTotal,
              (int) (memoryTotal - memoryFree), false);
      osNotification.contentView.setProgressBar(R.id.notification_2nd_bar, 100, (int) battLevel, false);
      break;

    case NotificationType.MEMORY_DISKIO:
      osNotification.contentView.setTextViewText(R.id.notification_1nd, "MEM: "
                                                 + UserInterfaceUtil.convertToSize(memoryFree, true));
      osNotification.contentView.setTextViewText(R.id.notification_2nd, "IO: "
                                                 + UserInterfaceUtil.convertToUsage(ioWaitUsage) + "%");
      osNotification.contentView.setProgressBar(R.id.notification_1nd_bar, (int) memoryTotal,
              (int) (memoryTotal - memoryFree), false);
      osNotification.contentView.setProgressBar(R.id.notification_2nd_bar, 100, (int) ioWaitUsage, false);
      break;

    case NotificationType.BATTERY_DISKIO:
      osNotification.contentView.setTextViewText(R.id.notification_1nd, "BAT: " + getBatteryInfo());
      osNotification.contentView.setTextViewText(R.id.notification_2nd, "IO: "
                                                 + UserInterfaceUtil.convertToUsage(ioWaitUsage) + "%");
      osNotification.contentView.setProgressBar(R.id.notification_1nd_bar, 100, (int) battLevel, false);
      osNotification.contentView.setProgressBar(R.id.notification_2nd_bar, 100, (int) ioWaitUsage, false);
      break;

    case NotificationType.NETWORKIO:
      osNotification.contentView.setTextViewText(R.id.notification_1nd, "OUT: "
                                                + UserInterfaceUtil.convertToSize(trafficOut, true));
      osNotification.contentView.setTextViewText(R.id.notification_2nd, "IN: "
                                                + UserInterfaceUtil.convertToSize(trafficIn, true));
      osNotification.contentView.setProgressBar(R.id.notification_1nd_bar,
                                                (int) (trafficOut + trafficIn), (int) trafficOut, false);
      osNotification.contentView.setProgressBar(R.id.notification_2nd_bar,
                                                (int) (trafficOut + trafficIn), (int) trafficIn, false);
      break;
    }

    osNotification.contentView.setTextViewText(R.id.notification_top1st,
        UserInterfaceUtil.convertToUsage(topUsage[0]) + "% " + topProcess[0]);
    osNotification.contentView.setTextViewText(R.id.notification_top2nd,
        UserInterfaceUtil.convertToUsage(topUsage[1]) + "% " + topProcess[1]);
    osNotification.contentView.setTextViewText(R.id.notification_top3nd,
        UserInterfaceUtil.convertToUsage(topUsage[2]) + "% " + topProcess[2]);

    // use customize font color
    if (fontColor == -1 && !CoreUtil.isGreaterThanIceCreamSandwich()) {
      osNotification.contentView.setTextColor(R.id.notification_2nd, Color.BLACK);
      osNotification.contentView.setTextColor(R.id.notification_1nd, Color.BLACK);
      osNotification.contentView.setTextColor(R.id.notification_cpu, Color.BLACK);
      osNotification.contentView.setTextColor(R.id.notification_top1st, Color.BLACK);
      osNotification.contentView.setTextColor(R.id.notification_top2nd, Color.BLACK);
      osNotification.contentView.setTextColor(R.id.notification_top3nd, Color.BLACK);
    }
    else if (fontColor != -1) {
      osNotification.contentView.setTextColor(R.id.notification_2nd, fontColor);
      osNotification.contentView.setTextColor(R.id.notification_1nd, fontColor);
      osNotification.contentView.setTextColor(R.id.notification_cpu, fontColor);
      osNotification.contentView.setTextColor(R.id.notification_top1st, fontColor);
      osNotification.contentView.setTextColor(R.id.notification_top2nd, fontColor);
      osNotification.contentView.setTextColor(R.id.notification_top3nd, fontColor);
    }

    // use customize background color
    if (backgroundColor != -1) {
      osNotification.contentView.setInt(R.id.custom_notification, "setBackgroundColor", backgroundColor);
    }


    /* Fix compatible issue for Notification Icon */
    if (!CoreUtil.isGreaterThanMarshmallow()) {
      osNotification.icon = iconColor;
      if (cpuUsage < 20)
        osNotification.iconLevel = 1;
      else if (cpuUsage < 40)
        osNotification.iconLevel = 2;
      else if (cpuUsage < 60)
        osNotification.iconLevel = 3;
      else if (cpuUsage < 80)
        osNotification.iconLevel = 4;
      else if (cpuUsage < 100)
        osNotification.iconLevel = 5;
      else
        osNotification.iconLevel = 6;
    }

    nManager.notify(NOTIFYID, osNotification);
  }

}
